#include "hidreactor.h"
#include <sys/types.h>
#include <dirent.h>
#include <QDebug>
#include <QApplication>
HidReactor::HidReactor(QObject *parent)
    : QObject(parent)
    , m_bRunning(false)
    , m_pThread(new QThread)
    , m_pSockNotifier(nullptr)
{
    this->moveToThread(m_pThread);
    connect(m_pThread,&QThread::started,this,&HidReactor::select);
}

HidReactor::~HidReactor()
{
    quit();
    m_pThread->deleteLater();
    for(auto& dev : m_hidDevices){
        dev->deleteLater();
        dev = nullptr;
    }
    m_hidDevices.clear();
    if(m_pSockNotifier){
        m_pSockNotifier->deleteLater();
    }
}

void HidReactor::openDevice(QString devPath)
{
    char name[1024];
    memset(name,0,sizeof(name));
    QString path("/dev/usb/");
    path.append(devPath);
    qDebug()<<"devPath:"<<path;
    int fd = open(path.toStdString().c_str(),O_RDWR | O_CLOEXEC);
    if(fd < 0){
        qDebug("open error");
        return;
    }
    if(getUsage(fd) != 2){
        return;
    }
    memset(name,0,sizeof(name));
    ioctl(fd,HIDIOCGNAME(sizeof(name)),name);

    auto dev = new HidDevice(fd);
    emit newDevice(QString(name),dev);
    m_hidDevices[devPath] = dev;
}

int HidReactor::findDevices()
{
    DIR* dir = opendir("/dev/usb/");
    if(dir == nullptr){
        qDebug("opendir failed");
        return 0;
    }

    struct dirent* ptr = nullptr;

    while((ptr = readdir(dir))){
        if(strcmp(".",ptr->d_name)==0 || strcmp("..",ptr->d_name)==0)
            continue;
        if(m_hidDevices.contains(ptr->d_name))
            continue;
        openDevice(ptr->d_name);
    }

    closedir(dir);
    if(m_pSockNotifier == nullptr){
        setupNotifier();
    }
    return m_hidDevices.size();
}

void HidReactor::select()
{
    fd_set rfds;
    while(m_bRunning){
        qApp->processEvents();
        FD_ZERO(&rfds);
        int maxfd = 0;

        for(auto& item: m_hidDevices){
            FD_SET(item->getHandle(),&rfds);
            maxfd = maxfd > item->getHandle() ? maxfd : item->getHandle();
        }
        int ret = ::select(maxfd+1, &rfds, NULL, NULL ,NULL);
        if(ret == -1 && errno == EINTR){
            continue;
        }
        if(ret < 0){
            perror("select");
            return;
        }
        qDebug("select finish");
        for(auto& item: m_hidDevices){
            if(FD_ISSET(item->getHandle(),&rfds)){
                    item->handleRead();
            }
        }
    }
}

void HidReactor::start()
{
    if(!m_pThread->isRunning()){
        m_bRunning = true;
        m_pThread->start();
    }
}

void HidReactor::quit()
{
    if(m_pThread->isRunning()){
        m_bRunning = false;
        m_pThread->quit();
        m_pThread->wait();
    }
}


// find telephony and consumer application,returnVal -1,-2 is error,2 success
int HidReactor::getUsage(int fd)
{
    int ret = 0;
    struct hiddev_devinfo info;

    if(ioctl(fd,HIDIOCGDEVINFO,&info) == -1){
        qDebug("HIDIOCGDEVINFO,error");
        return -1;
    }
    qDebug("HIDIOCGDEVINFO applicationNum:%d",info.num_applications);
    for(unsigned int i=0;i!=info.num_applications;i++){
        unsigned usage = ioctl(fd,HIDIOCAPPLICATION,i);
        if((int)usage == -1){
            qDebug("HIDIOCAPPLICATION error");
            return -2;
        }
        if(((unsigned short)(usage) == HID_USAGE_TELEPHOY_HEADSET || (unsigned short)(usage) == HID_USAGE_TELEPHOY_HANDSET) && ((unsigned short)(usage >> 16) == HID_USAGE_PAGE_TELEPHOY)){ //buttonDevice
            ret++;
            qDebug("find telephony application");
            continue;
        }
        if(((unsigned short)(usage) == HID_USAGE_CONSUMERCTRL) && ((unsigned short)(usage >> 16)  == HID_USAGE_PAGE_CONSUMER)){ //volumeDevice
            ret++;
            qDebug("find volume application");
            continue;
        }
        qDebug("usages of application[%u]: %0x\n",i,usage);
    }
    return ret;

}

//usb hot-swap
void HidReactor::setupNotifier()
{
    netLinkSocket =  socket(AF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);
    if(netLinkSocket == -1){
        qDebug("failed to create socket");
        return;
    }
    sockaddr_nl sa;
    memset(&sa,0,sizeof(sa));
    sa.nl_family = AF_NETLINK;
    sa.nl_pid = getpid();
    sa.nl_groups = -1;

    if(bind(netLinkSocket,(struct sockaddr*)&sa,sizeof(sa)) == -1){
        qDebug("failed to bind socket");
        return;
    }

    m_pSockNotifier = new QSocketNotifier(netLinkSocket,QSocketNotifier::Read);
    connect(m_pSockNotifier,&QSocketNotifier::activated,this,&HidReactor::handleNetLinkMsg);
}

void HidReactor::handleNetLinkMsg()
{
    char buff[1024];
    int len = recv(netLinkSocket,buff,sizeof(buff),0);
    if(len == -1){
        qDebug("failed to recv QSocketNotifier message");
        return;
    }
    buff[len] = '\0';
    QString msg(buff);
    if(msg.contains("hiddev")){
        auto devPath = msg.right(msg.length() - msg.lastIndexOf('/')-1);
        if(msg.contains("add@")){
            if(!m_hidDevices.contains(devPath))
                openDevice(devPath);
        }else if(msg.contains("remove@")){
            auto dev = m_hidDevices[devPath];
            dev->deleteLater();
            m_hidDevices.remove(devPath);
            emit delDevice(dev);
        }
    }
}

